using System;
using System.Collections.Generic;
using System.IO;
#if !SANDBOX
using System.Reflection;
#endif
using System.Xml.XPath;
using Microsoft.SharePoint;
using PS.Practices.Provisioner;
using PS.Practices.Provisioner.Provisioner;
using PS.Practices.Provisioner.Provisioner.Schema.Logic;
using PS.Practices.SiteBuilder.Features.Schema.DOM;
using PS.Practices.Util;
using File=System.IO.File;

namespace PS.Practices.SiteBuilder.Features.Schema
{
    public class Interpreter
    {
        public Interpreter()
        {
        }

        public Interpreter(IDictionary<string, string> properties)
        {
            Interpreter.properties = properties;
        }

        private static IDictionary<string, string> properties = new Dictionary<string, string>();
        private readonly List<Type> pluginTypes = new List<Type>();

        public bool ReopenWebOnApplyWebTemplate { get; set; }

        public void Process(SPWeb web, string filePath)
        {
            //Logger.Prefix = "Provisioner for " + web.ServerRelativeUrl;
            Logger.Info(string.Format("Applying provisioning logic based on script file: {0}", filePath));

            XPathDocument doc;
            try
            {
                doc = new XPathDocument(filePath);
            }
            catch (XPathException xex)
            {
                Logger.Error(xex);
                return;
            }
            
            Process(web, doc.CreateNavigator());
        }

        public void Process(SPWeb web, XPathNavigator navigator)
        {
            //Logger.Prefix = "Provisioner for " + web.ServerRelativeUrl;

            if (navigator.NodeType == XPathNodeType.Root)
            {
                navigator.MoveToChild(XPathNodeType.Element);
            }

            bool showErrors = ParserUtil.GetAttributeBool(navigator, "ShowErrors", false);

            try
            {
                WebParser.ApplyWebTemplate(web, navigator);
                WebParser.RestoreWebProperties(web, navigator);
                WebParser.RestoreCss(web, navigator);
                WebParser.FixupWebACL(web, navigator);
#if !SANDBOX
                WebParser.FixupFiles(web, navigator);
#endif
                ListParser.FixupLists(web, navigator);
                WebParser.RestrictSiteTemplates(web, navigator);
                WebParser.ProvisionWebs(web, navigator);
            }
            catch (Exception e)
            {
                Logger.Error(e);
                if (showErrors)
                    throw;
            }
        }
        
        public Extension[] GetExtensions(string script)
        {
            using (var sr = new StringReader(script))
            {
                XPathDocument xpd = new XPathDocument(sr);

                XPathNavigator xpn = xpd.CreateNavigator();

                List<Extension> res = new List<Extension>();

                XPathNodeIterator xNodeIter = xpn.Select("//Extensions/Extension");
                foreach (XPathNavigator navigator in xNodeIter)
                {
                    string assemblyName = ParserUtil.GetAttribute(navigator, "Assembly");
                    string tagPrefix = ParserUtil.GetAttribute(navigator, "TagPrefix");
                    if (!string.IsNullOrEmpty(assemblyName) && !string.IsNullOrEmpty(tagPrefix))
                    {
                        res.Add(new Extension(assemblyName, tagPrefix));
                    }
                }

                return res.ToArray();
            }
        }

        public void Process2(SPWeb web, string filePath)
        {
            if(File.Exists(filePath))
            {
                //Logger.Prefix = "Provisioner for " + web.ServerRelativeUrl;
                Logger.Info(string.Format("Applying provisioning logic based on script file: {0}", filePath));

                //Not necessary now
                //Prepocessor pr = new Prepocessor();
                //filePath = pr.Run(filePath);

                using (var fs = new StreamReader(filePath))
                {
                    var script = fs.ReadToEnd(); 
                    fs.Dispose();
                    ProcessScript(web, script);
                }
            }
            else
            {
                Logger.Error(new FileNotFoundException("Provisioner xml script not found", filePath));
            }
        }

        public void ProvisionWeb(ProvisionerAbstract provisioner)
        {
            SPListItem provisionerItem = provisioner.LocateGalleryProvisionerItem(provisioner.GalleryItemIdentifier);
            if(provisionerItem == null)
            {
                throw new Exception("Could not locate script associated with this provisioner job.  GalleryIdentifier: " + provisioner.GalleryItemIdentifier);
            }

            using (SPSite site = new SPSite(provisioner.TargetWebUrl))
            {
                using (SPWeb spWeb = site.OpenWeb())
                {
                    string url = ParserUtil.ReplaceAllPropertyFields(PS.Practices.Provisioner.Util.ListUtil.TryGetListItemProperty(provisionerItem, "WebUrl").ToString(), provisioner.Properties);
                    string name = ParserUtil.ReplaceAllPropertyFields(PS.Practices.Provisioner.Util.ListUtil.TryGetListItemProperty(provisionerItem, "WebTitle").ToString(), provisioner.Properties);
                    SPWebTemplate template = spWeb.GetWebTemplate((uint)1033, PS.Practices.Provisioner.Util.ListUtil.TryGetListItemProperty(provisionerItem, "Templates").ToString());

                    HandleEventFireing hef = new HandleEventFireing();
                    SPWeb provisionedWeb = null;
                    try
                    {
                        hef.CustomDisableEventFiring();
                        provisionedWeb = spWeb.Webs.Add(url, name, provisioner.Description, 1033,
                                                        template, false, false);

                        // Process the web
                        var locatedScript = provisionerItem["Script"].ToString();
                        ProcessScript(provisionedWeb, locatedScript, provisioner.Properties);
                    }
                    finally
                    {
                        hef.CustomEnableEventFiring();

                        if (provisionedWeb != null)
                            provisionedWeb.Dispose();
                    }
                }
            }
        }

        public void ProcessScript(SPWeb web, string script, Dictionary<string, object> properties)
        {
            Extension[] exs = GetExtensions(script);
            ProvisionerRoot root = DeSerializer<ProvisionerRoot>.DeserializeFromString(script, exs);
            root.ReopenWebOnApplyWebTemplate = this.ReopenWebOnApplyWebTemplate;
            root.Accept(new OmVisitorContext(web, properties));
        }

        public void ProcessScript(SPWeb web, string script)
        {
            Extension[] exs = GetExtensions(script);
            ProvisionerRoot root = DeSerializer<ProvisionerRoot>.DeserializeFromString(script, exs);
            root.ReopenWebOnApplyWebTemplate = this.ReopenWebOnApplyWebTemplate;
            root.Accept(new OmVisitorContext(web, new Dictionary<string, object>()));
        }

        public void ProcessScriptSandboxed(SPWeb web, string script, ISandboxLogger logger )
        {
            try
            {
                Logger.SandboxLogger = logger;
                ProvisionerRoot root = DeSerializer<ProvisionerRoot>.DeserializeFromStringStatic(script);
                //TODO: Write new visitor specifically for sandbox solutions
                root.Accept(new OmVisitorContext(web, new Dictionary<string, object>()));
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
        }

        public void Process(SPList list, XPathNavigator script)
        {
            ListParser.ProcessList(list, script);
        }

#if !SANDBOX
        public void LoadPlugin(string filePath)
        {
            Assembly assembly = Assembly.LoadFile(filePath);
            Type[] publicTypes = assembly.GetExportedTypes();
            foreach (Type type in publicTypes)
            {
                if(type.BaseType == typeof(WebOperation))
                {
                     pluginTypes.Add(type);
                }
            }
        }
#endif

        public void RunPlugins(SPWeb web)
        {
            foreach (Type type in pluginTypes)
            {
                try
                {
                    object o = Activator.CreateInstance(type);
                    WebOperation webOp = (WebOperation)o;
                    webOp.Accept(new OmWebOperationVisitor(web, new Dictionary<string, object>()));
                }
                catch (Exception e)
                {
                    Logger.Error(e);
                }
            }
        }
    }

    public class HandleEventFireing : SPWebEventReceiver
    {
        public HandleEventFireing()
        {
        }

        public void CustomDisableEventFiring()
        {
            this.EventFiringEnabled = false;
        }

        public void CustomEnableEventFiring()
        {
            this.EventFiringEnabled = true;
        }

    }
}
